﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Postnuclear.Test.Helpers;

namespace Postnuclear.Test
  {
  /// <summary>
  /// Represents a test class for the <see cref="WeakEventHandler{TTarget, TEventArgs}"/> class.
  /// </summary>
  [TestClass]
  public sealed class WeakEventHandlerTest
    : TestClassBase
    {
    #region Public

    // Methods

    /// <summary>
    /// Initializes this instance.
    /// </summary>
    [TestInitialize]
    public void Initialize()
      {
      Event = new Mock<INotifyValueChanged>();
      }

    /// <summary>
    /// Tests subscribing a weak referenced event handler.
    /// </summary>
    [TestMethod]
    public void WeakEventHandlerSubscriptionTest()
      {
      int count = 0;
      Mock<IHandlerProvider<EventArgs>> handler = new Mock<IHandlerProvider<EventArgs>>();
      handler.Setup(m => m.Handle(It.IsAny<object>(), EventArgs.Empty)).Callback(() => ++count);
      Event.Object.ValueChanged += new EventHandler<EventArgs>(handler.Object.Handle).MakeWeak(eh => Event.Object.ValueChanged -= eh);
      Assert.AreEqual(0, count);

      Event.RaiseValueChanged();
      Assert.AreEqual(1, count);

      // Force a garbage collection.
      GarbageCollector.CollectAndWaitForPendingFinalizers();

      Event.RaiseValueChanged();
      Assert.AreEqual(2, count);

      GC.KeepAlive(handler);
      }

    /// <summary>
    /// Tests subscribing an anonymous event handler. Only instance methods are supported.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void WeakEventHandlerSubscriptionAnonymousHandlerTest()
      {
      Event.Object.ValueChanged += new EventHandler<EventArgs>((o, e) => e.ToString()).MakeWeak(eh => Event.Object.ValueChanged -= eh);
      }

    /// <summary>
    /// Tests subscribing a static event handler. Only instance methods are supported.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void WeakEventHandlerSubscriptionStaticHandlerTest()
      {
      Event.Object.ValueChanged += new EventHandler<EventArgs>(OnHandleEventStatically).MakeWeak(eh => Event.Object.ValueChanged -= eh);
      }

    /// <summary>
    /// Tests the automatic unsubscription of an event handler after garbage collection.
    /// </summary>
    [TestMethod]
    public void WeakEventHandlerAutomaticUnsubscriptionTest()
      {
      int count = 0;
      Mock<IHandlerProvider<EventArgs>> handler = new Mock<IHandlerProvider<EventArgs>>();
      WeakReference<IHandlerProvider<EventArgs>> handlerReference = new WeakReference<IHandlerProvider<EventArgs>>(handler.Object);
      handler.Setup(m => m.Handle(It.IsAny<object>(), EventArgs.Empty)).Callback(() => ++count);
      Event.Object.ValueChanged += new EventHandler<EventArgs>(handler.Object.Handle).MakeWeak(eh => Event.Object.ValueChanged -= eh);

      // Remove all strong references and launch GC
      handler = null;
      GarbageCollector.CollectAndWaitForPendingFinalizers();

      // The weak event handler is only unsubscribed the first next time raising the event.
      Event.RaiseValueChanged();
      Assert.IsFalse(handlerReference.IsAlive);
      Assert.AreEqual(0, count);
      }

    #endregion

    #region Private

    // Properties

    private Mock<INotifyValueChanged> Event
      {
      get;
      set;
      }

    // Methods

    private static void OnHandleEventStatically(object sender, EventArgs e)
      {
      }

    #endregion
    }
  }
